外键(一对多)
1. 使用 ForeignKey 创建外键
- 在建立外键(即: 一对多关系)的时候,ForeignKey() 一般定义在多的一方(即: 一对多关系中多的一方的表类中)
- models.ForeignKey(to='表的类名', to_field='字段名', related_name='反向操作使用的属性名')
- ForeignKey 的参数解释:
- to -> 设置需要关联表的类名
- to='Classes' -> 加上引号是为了在当前models.py文件中通过反射去查询表的类
- to=Classes -> 不加引号,直接使用表的类,如果Classes类定义在to的表类下面那么就会报错,因为Classes未定义就引用了
-> 不加引号的使用场景: 使用导入的表类时无需加引号(不会出现上面的报错情况,因为表类在文件一开始就已经导入了)
- to_field -> 设置关联表的那个字段,如果不传默认关联 id
- related_name -> 反向操作时,使用的属性名,用于代替原反向查询时的 '表名_set' 的写法 -> (通俗理解: 给主表起的别名,用于反向操作)
- related_query_name -> 反向查询操作时,使用的连接前缀,用于替换表名
- on_delete -> 当删除关联表中的数据时,当前表与其关联的行的行为
# models.ForeignKey(to='表的类名', to_field='字段名', related_name='反向操作使用的属性名')
# 班级表
class Classes(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
# 学生表
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
age = models.IntegerField()
classes = models.ForeignKey(to='Classes', related_name='fkclass') # 建立外键,且 classes 就是外键属性
8.on_delete -> 外键方法参数
- 当删除关联表中的数据时,当前表与其关联的行的行为
- Django1+ 中默认设置了 on_delete=models.CASCADE
- 注意: 在Django2+中创建外键时一定要设置该参数,不然就会报错
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
classes = models.ForeignKey(to='Classes', on_delete=models.CASCADE)
- on_delete 的值:
- models.CASCADE -> 删除关联数据,与之关联也删除
- models.DO_NOTHING -> 删除关联数据,引发错误IntegrityError
- models.PROTECT -> 删除关联数据,引发错误ProtectedError
- models.SET_NULL -> 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
- models.SET_DEFAULT -> 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
- models.SET -> 删除关联数据
- 与之关联的值设置为指定值,设置:models.SET(值)
- 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
# models.SET 的演示
def func():
return 10
class MyModel(models.Model):
user = models.ForeignKey(
to="User",
to_field="id",
on_delete=models.SET(func)
)
9.db_constraint -> 外键方法参数
- 是否在数据库中创建外键约束,默认为True
- 通俗理解: 如果 db_constraint=False ,虽然通过 ForeignKey 字段类建立了索引,但是实际上在数据库中是没有建立,只是说明了该字段有外键的功能,一般用于软外键
- 软外键的说明: 不在表中建立外键约束,从代码层面上建立外键约束
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
classes = models.ForeignKey(to='Classes', db_constraint=True)
外键字段相关
1. 使用外键字段的注意事项
- 使用 orm 所创建的外键都会在外键字段名的结尾处加上 _id,除非使用了 db_column='xxx' 去重新设置该外键字段的字段名(如果使用 db_column 重新设置外键字段名,不要和类中的外键属性名重复,因为该属性名是用于正向查询的(即:存放着与主表数据相关的从表数据的对象))
_files/Image.png)
- 使用数据库表中的外键名(xxx_id)去对外键字段中的数据进行添加和修改操作(查询、删除、批量修改除外),而不是直接使用类中的外键字段名,因为类中的外键字段名是用于正向查询(即:存放着与主表数据相关的从表数据的对象)
# 正确示范 -> 这里使用了上面的表的类做示范
# 增加数据
models.Student.objects.create(name='Kevin', classes_id=1)
# 查询数据
models.Student.objects.get(classes_id=1)
models.Student.objects.get(classes=1)
# 删除数据
models.Student.objects.get(classes_id=1).delete()
models.Student.objects.get(classes=1).delete()
# 修改数据
obj = models.Student.objects.get(classes_id=1)
obj = models.Student.objects.get(classes=1)
obj.classes_id= 2
obj.save()
# 错误示范 -> 这里使用了上面的表的类做示范
# 增加数据
models.Student.objects.create(name='Kevin', classes=1)
# 修改数据
obj = models.Student.objects.get(classes=1)
obj.classes = 2
obj.save()
2. 两种修改外键数据的方法
- 方法一 -> 通过数据库中的外键名进行修改 -> 推荐使用
# 这里使用了上面的表的类做示范
obj = models.Student.objects.get(classes_id=1)
obj.classes_id= 2
obj.save()
- 方法二 -> 对类中的外键属性进行重新的赋值
# 这里使用了上面的表的类做示范
class_obj = Classes.objects.get(id=5)
student_obj = Student.objects.get(id=4)
student_obj.classes = class_obj # 对外键属性进行重新赋值,且该值必须是班级对象,因为 classes 的外键属性返回的就是班级对象
student_obj.save()
# 错误示范: 不要直接修改外键属性所返回的对象,因为直接修改该对象相当于直接修改了从表(班级表)中的内容
student_obj = Student.objects.get(id=4)
student_obj.classes.id = 2
student_obj.classes.save()
3. 外键字段接收的参数
- 在进行正删改查的时候,如果涉及到外键字段,那么外键字段可以接收两种类型的参数:
- 对象 -> 外键字段属性名 = 对象 -> 对象一般代指的是查询数据后得到的对象(因为 .外键字段属性名 返回的是一个对象,所以它可以接收一个对象)
- 数字 -> 外键字段属性名_id = 数字
- 外键字段接收的参数是 对象 的相关操作
# 增
classes_obj = Classes.objects.filter(id=1).first()
Student.objects.create(name='Yeung', age=18, classes=classes_obj)
# 改
classes = Classes.objects.filter(id=2).first()
student = Student.objects.filter(name='Yeung').first()
student.classes = classes
student.save()
# 查
classes = Classes.objects.filter(id=1).first()
student = Student.objects.filter(classes=classes)
- 外键字段接收的参数是 数字 的相关操作
# 增
Student.objects.create(name='Yeung', age=18, classes_id=1)
# 改
student = Student.objects.filter(name='Yeung').first()
student.classes_id = 1
student.save()
# 查
student = Student.objects.filter(classes_id=1)
- 在查询数据 或 批量修改外键字段的数据的时候,外键字段可以不用加 _id 直接传递数字类型的参数
- 查询数据
# classes 是外键字段
student = Student.objects.filter(classes=1) # 传递的参数是数字类型
# ------------------------------------------------------
classes_obj = Classes.objects.filter(id=1).first()
student = Student.objects.filter(classes=classes_obj) # 传递的参数是对象
# ------------------------------------------------------
student = Student.objects.get(classes=2) # 传递的参数是数字类型
# ------------------------------------------------------
classes_obj = Classes.objects.filter(id=2).first()
student = Student.objects.get(classes=classes_obj) # 传递的参数是对象
- 批量修改外键字段的数据
# classes 是外键字段
student = Student.objects.filter(classes=1)
student.update(classes=2) # 传递的参数是数字类型
# ------------------------------------------------------
student = Student.objects.filter(classes=1)
classes_obj = Classes.objects.filter(id=2).first()
student.update(classes=classes_obj) # 传递的参数是数字类型
查询相关
1. 正向查询 -> 主表获取从表的数据
- 作用: 简化连表获取数据的操作
- 正向查询用字段,反向查询用表名
- 写法一:
- 通过对象查找
- 语法: 查询到的对象.类的外键属性 -> 返回值:主表数据相关的从表数据的对象
# Django会将从表的所有数据放进表的类中的外键属性中,当要获取与主表数据相关的从表数据的时候,外键属性就会返回与主表数据相关的从表数据的对象
# 这里使用了上面的表的类做示范
class_obj = Student.objects.get(id=2).classes # <Classes: Classes object> -> 返回值: 与查询到的 student 数据相关的 class 数据的对象
class_name = Student.objects.get(id=2).classes.name # '一班'
- 写法二:
- 使用 values 或 values_list 通过字段查询
- 语法:
- .values('主表字段名', '主表字段名', '主表类的外键属性名__从表字段名', '主表类的外键属性名__主表类的外键属性名下的外键属性名__从表字段名')
- .values_list('主表字段名', '主表字段名', '主表类的外键属性名__从表字段名', '主表类的外键属性名__主表类的外键属性名下的外键属性名__从表字段名')
student_obj1 = Student.objects.all().values('name', 'classes__id', 'classes__name') # name是Student表的字段名,而classes__id中的classes是Student类中所创建的外键属性,id则是classes表中的字段
student_obj2 = Student.objects.all().values_list('name', 'classes__id', 'classes__name')
print(student_obj1) # <QuerySet [{'classes__name': '一班', 'name': 'Kevin', 'classes__id': 1}, {'classes__name': '三班', 'name': 'Aimer', 'classes__id': 4}]>
print(student_obj2) # <QuerySet [('Kevin', 1, '一班'), ('Aimer', 4, '三班')]>
- 跨多张表进行查询 -> 查找书名是“西游记”的书的作者的年龄和手机号码和作者详情id(是ORM练习中的最后一题)
author_list = Book.objects.filter(title='西游记').values('author__name', 'author__detail_id', 'author__detail__addr', 'author__detail__email')
2. 反向查询 -> 从表获取主表的数据
- 作用: 简化连表获取数据的操作
- 正向查询用字段,反向查询用表名
- 写法一:
- 通过对象查找
- 语法: 查询到的对象.主表的类名_set.all/get/filter() -> 主表类名首字母无需大写
# 这里使用了上面的表的类做示范
# 例子: 查询一班的所有学生
student_list = Classes.objects.get(id=1).student_set.all() # <QuerySet [<Student: Student object>, <Student: Student object>]>
for student in student_list:
print(student.name) # Kevin ……
- related_name
- 用于替换 主表的类名_set 的写法
- 在表类的外键属性(即: ForeignKey()方法)中添加 related_name='xxx' 即可
- 如果在表类的外键属性中添加了 related_name='xxx',那么 主表的类名_set 的写法就会无效
# models.py
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
classes = models.ForeignKey(to='Classes', related_name='fkclass')
# views.py
student_list = Classes.objects.get(id=1).fkclass.all() # <QuerySet [<Student: Student object>, <Student: Student object>]>
for student in student_list:
print(student.name) # Kevin ……
- 写法二:
- 使用 values 或 values_list 通过字段查询
- 语法:
- 如果没有设置表类中外键属性的 related_name
- .values('从表字段名', '从表字段名', '主表类名__主表字段名', '主表类名__主表字段名') -> 主表类名无需大写
- .values_list('从表字段名', '从表字段名', '主表类名__主表字段名', '主表类名__主表字段名') -> 主表类名无需大写
# models.py
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
classes = models.ForeignKey(to='Classes')
student_list1 = Classes.objects.all().values('name', 'student__name')
student_list2 = Classes.objects.all().values_list('name', 'student__name')
print(student_list1) # <QuerySet [{'student__name': 'Kevin', 'name': '一班'}, {'student__name': 'Aimer', 'name': '三班'}, ……]>
print(student_list2) # <QuerySet [('一班', 'Kevin'), ('三班', 'Aimer'), ('一班', 'Timmy'), ('二班', None), ('四班', None), ('五班', None)]>
- 如果设置了表类中外键属性的 related_name
- .values('从表字段名', '从表字段名', 'related_name所设置的名字__主表字段名', 'related_name所设置的名字__主表字段名')
- .values_list('从表字段名', '从表字段名', 'related_name所设置的名字__主表字段名', 'related_name所设置的名字__主表字段名')
# models.py
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
classes = models.ForeignKey(to='Classes', related_name='fkclass')
student_list1 = Classes.objects.all().values('name', 'fkclass__name')
student_list2 = Classes.objects.all().values_list('name', 'fkclass__name')
print(student_list1) # <QuerySet [{'fkclass__name': 'Kevin', 'name': '一班'}, {'fkclass__name': 'Aimer', 'name': '三班'}, ……]>
print(student_list2) # <QuerySet [('一班', 'Kevin'), ('三班', 'Aimer'), ('一班', 'Timmy'), ('二班', None), ('四班', None), ('五班', None)]>
QuerySet方法大全
##################################################################
# PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
##################################################################
def all(self)
# 获取所有的数据对象
def filter(self, *args, **kwargs)
# 条件查询
# 条件可以是:参数,字典,Q
def exclude(self, *args, **kwargs)
# 条件查询
# 条件可以是:参数,字典,Q
def select_related(self, *fields)
性能相关:表之间进行join连表操作,一次性获取关联的数据。
总结:
1. select_related主要针一对一和多对一关系进行优化。
2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。
def prefetch_related(self, *lookups)
性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。
总结:
1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。
def annotate(self, *args, **kwargs)
# 用于实现聚合group by查询
from django.db.models import Count, Avg, Max, Min, Sum
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
# SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
# SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
# SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1
def distinct(self, *field_names)
# 用于distinct去重
models.UserInfo.objects.values('nid').distinct()
# select distinct nid from userinfo
注:只有在PostgreSQL中才能使用distinct进行去重
def order_by(self, *field_names)
# 用于排序
models.UserInfo.objects.all().order_by('-id','age')
def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
# 构造额外的查询条件或者映射,如:子查询
Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])
def reverse(self):
# 倒序
models.UserInfo.objects.all().order_by('-nid').reverse()
# 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序
def defer(self, *fields):
models.UserInfo.objects.defer('username','id')
# 或
models.UserInfo.objects.filter(...).defer('username','id')
# 映射中排除某列数据
def only(self, *fields):
# 仅取某个表中的数据
models.UserInfo.objects.only('username','id')
# 或
models.UserInfo.objects.filter(...).only('username','id')
def using(self, alias):
# 指定使用的数据库,参数为别名(setting中的设置)
##################################################
# PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
##################################################
def raw(self, raw_query, params=None, translations=None, using=None):
# 执行原生SQL
models.UserInfo.objects.raw('select * from userinfo')
# 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名
models.UserInfo.objects.raw('select id as nid from 其他表')
# 为原生SQL设置参数
models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])
# 将获取的到列名转换为指定列名
name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
# 指定数据库
models.UserInfo.objects.raw('select * from userinfo', using="default")
################### 原生SQL ###################
from django.db import connection, connections
cursor = connection.cursor() # cursor = connections['default'].cursor()
cursor.execute("""SELECT * from auth_user where id = %s""", [1])
row = cursor.fetchone() # fetchall()/fetchmany(..)
def values(self, *fields):
# 获取每行数据为字典格式
def values_list(self, *fields, **kwargs):
# 获取每行数据为元祖
def dates(self, field_name, kind, order='ASC'):
# 根据时间进行某一部分进行去重查找并截取指定内容
# kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
# order只能是:"ASC" "DESC"
# 并获取转换后的时间
- year : 年-01-01
- month: 年-月-01
- day : 年-月-日
models.DatePlus.objects.dates('ctime','day','DESC')
def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
# 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间
# kind只能是 "year", "month", "day", "hour", "minute", "second"
# order只能是:"ASC" "DESC"
# tzinfo时区对象
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))
"""
pip3 install pytz
import pytz
pytz.all_timezones
pytz.timezone(‘Asia/Shanghai’)
"""
def none(self):
# 空QuerySet对象
####################################
# METHODS THAT DO DATABASE QUERIES #
####################################
def aggregate(self, *args, **kwargs):
# 聚合函数,获取字典类型聚合结果
from django.db.models import Count, Avg, Max, Min, Sum
result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
===> {'k': 3, 'n': 4}
def count(self):
# 获取个数
def get(self, *args, **kwargs):
# 获取单个对象
def create(self, **kwargs):
# 创建对象
def bulk_create(self, objs, batch_size=None):
# 批量插入
# batch_size表示一次插入的个数
objs = [
models.DDD(name='r11'),
models.DDD(name='r22')
]
models.DDD.objects.bulk_create(objs, 10)
def get_or_create(self, defaults=None, **kwargs):
# 如果存在,则获取,否则,创建
# defaults 指定创建时,其他字段的值
obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})
def update_or_create(self, defaults=None, **kwargs):
# 如果存在,则更新,否则,创建
# defaults 指定创建时或更新时的其他字段
obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})
def first(self):
# 获取第一个
def last(self):
# 获取最后一个
def in_bulk(self, id_list=None):
# 根据主键ID进行查找
id_list = [11,21,31]
models.DDD.objects.in_bulk(id_list)
def delete(self):
# 删除
def update(self, **kwargs):
# 更新
def exists(self):
# 是否有结果